﻿using System;
using System.Collections.Generic;
using System.Linq;

using NHibernate;
using NHibernate.Linq;
using System.Linq.Expressions;
using System.Data;
using Laugh.Data.DatabaseCfg;
using System.Collections.Concurrent;

namespace Laugh.Data
{
    public class DataRepository<T> : IRepository<T> where T : class, IEntity
    {
        private string databaseKey = "default";
        private ISessionFactoryHolder _sessionFactoryHolder;
        ConcurrentDictionary<string, ISession> _sessions = new ConcurrentDictionary<string, ISession>(StringComparer.OrdinalIgnoreCase);

        public DataRepository(ISessionFactoryHolder sessionFactoryHolder)
        {
            _sessionFactoryHolder = sessionFactoryHolder;
        }

        public IRepository<T> UseDatabase(string key)
        {
            databaseKey = key;
            return this;
        }

        public ITransaction BeginTransaction()
        {
            return new DataTransaction(Session);
        }

        #region save,delete

        public virtual void Delete(T entity)
        {
            TryCatch(() =>
            {
                Session.Delete(entity);
                return null;
            });
        }

        public virtual void DeleteAll()
        {
            TryCatch(() =>
            {
                Session.Delete(String.Format("from {0} ", typeof(T).Name));
                return null;
            });
        }

        public virtual void DeleteBy(int id)
        {
            TryCatch(() =>
            {
                Session.Delete(Session.Get<T>(id));
                return null;
            });
        }

        public virtual void DeleteById(object id)
        {
            TryCatch(() =>
            {
                Session.CreateSQLQuery("delete from " + typeof(T).Name + " where id = ?").
                    SetParameter(0, id).ExecuteUpdate();
                return null;
            });
        }

        public virtual T Update(T entity)
        {
            return TryCatch(() =>
            {
                Session.Update(entity);
                return entity;
            });
        }

        public virtual T SaveOrUpdate(T entity)
        {
            return TryCatch(() => Session.Merge(entity) as T);
        }

        public virtual T Save(T entity)
        {
            return TryCatch(() =>
            {
                Session.Save(entity);
                return entity;
            });
        }

        public virtual void Evict(T entity)
        {
            Session.Evict(entity);
        }

        public void Detach(T entity)
        {
            Session.Evict(entity);
        }

        public void Attach(T entity)
        {
            Session.Update(entity);
        }

        public void Refresh(T entity)
        {
            Session.Refresh(entity, LockMode.None);
        }

        public virtual void Copy(T source, T target)
        {
            var metadata = Session.SessionFactory.GetClassMetadata(typeof(T));
            var values = metadata.GetPropertyValues(source, EntityMode.Poco);
            metadata.SetPropertyValues(target, values, EntityMode.Poco);
        }

        public void Initialize(object obj)
        {
            NHibernateUtil.Initialize(obj);
        }
        #endregion

        #region  Get
        public virtual T Get(object id)
        {
            return Session.Get<T>(id);
        }

        public virtual void Load(T newObj, object id)
        {
            Session.Load(newObj, id);
        }

        public virtual T Get(Expression<Func<T, bool>> predicate)
        {
            return Query.SingleOrDefault(predicate);
        }

        #endregion

        #region count,exists
        public virtual int Count()
        {
            return Count((Expression<Func<T, bool>>)null);
        }

        public virtual int Count(Expression<Func<T, bool>> predicate)
        {
            var result = Query;

            if (predicate != null)
                result = result.Where(predicate);

            return result.Count();
        }

        public virtual bool Exists()
        {
            return Count() > 0;
        }

        public virtual bool Exists(Expression<Func<T, bool>> predicate)
        {
            return Count(predicate) > 0;
        }

        #endregion

        #region fetch
        public virtual IQueryable<T> All
        {
            get { return Query; }
        }

        public virtual IQueryable<T> Fetch(Expression<Func<T, bool>> predicate)
        {
            return Query.Where(predicate);
        }
        #endregion

        #region 私有
        private ISession Session
        {
            get
            {
                return _sessions.GetOrAdd(databaseKey, key => _sessionFactoryHolder.GetSessionFactory(key).OpenSession());
            }
        }

        public void Dispose()
        {
            if (_sessions.Count > 0)
                _sessions.ForEach(i =>
                {
                    i.Value.Flush();
                    i.Value.Close();
                    i.Value.Dispose();
                });

            _sessions = null;
        }

        private IQueryable<T> Query
        {
            get
            {
                var query = Session.Query<T>();

                return query;
            }
        }

        private T TryCatch(Func<T> func)
        {
            try
            {
                return func();
            }
            catch (UnresolvableObjectException exception)
            {
                throw new DataException(exception.Message);
            }
            catch (NonUniqueObjectException exception)
            {
                throw new DataException(exception.Message);
            }
            catch (Exception exception)
            {
                throw new DataException(exception.Message);
            }
        }

        #endregion
    }
}
